﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Ubatic.Contracts.Services;
using Ubatic.Contracts.Contracts;
using Ubatic.Data;
using Web.Framework.Mapper;
using Web.Framework.Encrypt;
using System.Transactions;
using System.IO;
using System.Web;
using Ubatic.Contracts.Enums;

namespace Ubatic.Services
{
    public class UserService : IUserService
    {
        private const string salt = "Ubatic.Services.UserService";

        public UsuarioDto RegistrarUsuario(UsuarioDto usuario)
        {
            string guidForEmail = Guid.NewGuid().ToString();
            using (TransactionScope tScope = new TransactionScope())
            {
                using (var ctx = new UbaticEntities())
                {
                    Usuario dbUserExistente = ctx.Usuario.Where(u => u.Cuil == usuario.Cuil && u.CorreoVerificado == true).SingleOrDefault();
                    if (dbUserExistente != null)
                    {
                        throw new Exception("Ya existe un usuario con ese CUIL");
                    }

                    dbUserExistente = ctx.Usuario.Where(u => u.Cuil == usuario.Cuil && u.CorreoVerificado == false).SingleOrDefault();
                    if (dbUserExistente != null)
                    {
                        // Elimina el usuario anterior
                        foreach (var ur in dbUserExistente.UsuarioRol)
                        {
                            ctx.UsuarioRols.DeleteObject(ur);
                        }

                        dbUserExistente.UsuarioRol.Clear();

                        ctx.UsuarioContrasenia.DeleteObject(dbUserExistente.UsuarioContrasenia);
                        ctx.Usuario.DeleteObject(dbUserExistente);
                        ctx.SaveChanges();
                    }

                    usuario.CodigoVerificador = guidForEmail;

                    // Creo el usuario
                    Usuario dbUsuario = new Usuario();
                    ctx.Usuario.AddObject(dbUsuario);
                    EntityMapper.Map(usuario, dbUsuario);

                    //Mapeo la contraseña
                    dbUsuario.UsuarioContrasenia = new UsuarioContrasenia();
                    dbUsuario.UsuarioContrasenia.Contrasenia = EncryptHelper.HashPassword(usuario.Password, salt);

                    // Mapeo los roles
                    foreach (int roleId in usuario.Roles)
                    {
                        UsuarioRol usuarioRol = new UsuarioRol();
                        usuarioRol.RolId = roleId;
                        dbUsuario.UsuarioRol.Add(usuarioRol);
                    }

                    dbUsuario.FechaAlta = DateTime.Now;
                    dbUsuario.EstadoAutorizacionId = (int)EstadoAutorizacionUsuarioEnum.NoAutorizado;

                    ctx.SaveChanges();

                    EnviarEmailDeConfirmacionCorreoElectronico(usuario);

                    // si envía el email completo la transacción
                    tScope.Complete();

                    usuario.Id = dbUsuario.Id;
                }
            }

            return usuario;
        }

        public void EnviarContraseniaProvisoria(long userId)
        {
            using (TransactionScope tScope = new TransactionScope())
            {
                using (var ctx = new UbaticEntities())
                {
                    var dbUsuario = ctx.Usuario.Where(u => u.Id == userId).SingleOrDefault();
                    if (dbUsuario != null)
                    {
                        string pseudoClave = Guid.NewGuid().ToString().Substring(0, 8);
                        dbUsuario.UsuarioContrasenia.ForzarCambioContrasenia = true;                        
                        dbUsuario.UsuarioContrasenia.Contrasenia = EncryptHelper.HashPassword(pseudoClave, salt);
                        ctx.SaveChanges();                        
                        EnviarEmailDeNuevaClaveAutogenerada(dbUsuario, pseudoClave);
                        tScope.Complete();
                    }
                }
            }
        }

        private static void EnviarEmailDeNuevaClaveAutogenerada(Usuario usuario, string clave)
        {
            //Envio el email
            EmailService emailService = new EmailService();
            emailService.LoadFromConfig();
            emailService.Body = File.ReadAllText(Path.Combine(emailService.BodyPath, "MailClaveProvisoria.htm"));

            // Reemplazo los campos
            emailService.Body = emailService.Body.Replace("#Nombre#", usuario.Nombre);
            emailService.Body = emailService.Body.Replace("#Apellido#", usuario.Apellido);
            emailService.Body = emailService.Body.Replace("#newpassword#", clave);

            emailService.SendEmail(usuario.CorreoElectronico);
        }

        private static void EnviarEmailDeConfirmacionCorreoElectronico(UsuarioDto usuario)
        {
            //Envio el email
            EmailService emailService = new EmailService();
            emailService.LoadFromConfig();
            emailService.Body = File.ReadAllText(Path.Combine(emailService.BodyPath, "MailRegistracion.htm"));

            // Reemplazo los campos
            emailService.Body = emailService.Body.Replace("#Nombre#", usuario.Nombre);
            emailService.Body = emailService.Body.Replace("#Apellido#", usuario.Apellido);
            emailService.Body = emailService.Body.Replace("#VERIFICADOR#", usuario.CodigoVerificador);

            emailService.SendEmail(usuario.CorreoElectronico);
        }

        public bool ConfirmarEmail(string hash, string password)
        {
            bool confirmaEmail = false;
            using (TransactionScope tScope = new TransactionScope())
            {
                using (var ctx = new UbaticEntities())
                {
                    string passwordHash = EncryptHelper.HashPassword(password, salt);
                    var dbUsuario = ctx.Usuario.Where(u => u.CodigoVerificador == hash).SingleOrDefault();
                    if (dbUsuario != null)
                    {
                        if (dbUsuario.UsuarioContrasenia.Contrasenia == passwordHash)
                        {
                            confirmaEmail = true;
                            dbUsuario.CorreoVerificado = true;

                            // TODO: autorizar contra el web service

                            dbUsuario.EstadoAutorizacionId = (int)EstadoAutorizacionUsuarioEnum.CorreoVerificado;
                        }
                    }

                    ctx.SaveChanges();
                    tScope.Complete();
                }
            }

            return confirmaEmail;
        }

        public UsuarioDto AutorizarUsuario(long cuit, string password)
        {
            UsuarioDto usuarioDto = null;
            using (TransactionScope tScope = new TransactionScope())
            {
                using (var ctx = new UbaticEntities())
                {
                    int autorizado = (int)EstadoAutorizacionUsuarioEnum.Autorizado;
                    var dbUsuario = ctx.Usuario.Where(u => u.Cuil == cuit && u.EstadoAutorizacionId == autorizado).SingleOrDefault();
                    if (dbUsuario != null)
                    {
                        if (dbUsuario.UsuarioContrasenia.Contrasenia == EncryptHelper.HashPassword(password, salt))
                        {
                            usuarioDto = new UsuarioDto();
                            EntityMapper.Map(dbUsuario, usuarioDto);

                            foreach (var dbRole in dbUsuario.UsuarioRol)
                            {
                                usuarioDto.Roles.Add(dbRole.RolId);
                            }
                        }                      
                    }

                    tScope.Complete();
                }
            }

            return usuarioDto;
        }

        public UsuarioDto CambiarContraseña(long userId, string oldPassword, string newPassword)
        {
            throw new NotImplementedException();
        }

        public List<UsuarioDto> ObtenerUsuarios(CriteriaUsuarioDto criteria)
        {
            using (var ctx = new UbaticEntities())
            {
                IQueryable<Usuario> query = ctx.Usuario;

                if (criteria.Cuil.HasValue)
                {
                    query = query.Where(c => c.Cuil == criteria.Cuil);
                }

                if (!string.IsNullOrEmpty(criteria.Nombre))
                {
                    query = query.Where(c => c.Nombre.ToUpper().Contains(criteria.Nombre.ToUpper()));
                }

                if (!string.IsNullOrEmpty(criteria.Apellido))
                {
                    query = query.Where(c => c.Apellido.ToUpper().Contains(criteria.Apellido.ToUpper()));
                }

                if (criteria.TipoDocumentoId.HasValue)
                {
                    query = query.Where(c => c.TipoDocumentoId == criteria.TipoDocumentoId);
                }

                if (criteria.NroDocumento.HasValue)
                {
                    query = query.Where(c => c.NroDocumento == criteria.NroDocumento);
                }

                if (criteria.DependenciaId.HasValue)
                {
                    query = query.Where(c => c.DependenciaId == criteria.DependenciaId);
                }

                if (!string.IsNullOrEmpty(criteria.CorreoElectronico))
                {
                    query = query.Where(c => c.CorreoElectronico.ToUpper().Contains(criteria.CorreoElectronico.ToUpper()));
                }

                return ToDtoList(query.ToList());
            }
        }      

        public UsuarioDto ObtenerUsuario(long usuarioId)
        {
            using (var ctx = new UbaticEntities())
            {
                Usuario usuario = this.ObtenerUsuario(ctx, usuarioId);
                UsuarioDto dto = null;
                if (usuario != null)
                {
                    dto = new UsuarioDto();
                    EntityMapper.Map(usuario, dto);
                    if (usuario.UsuarioContrasenia != null)
                    {
                        UsuarioContrasenia usuarioContrasenia = ctx.UsuarioContrasenia.First();
                        dto.Password = usuarioContrasenia.Contrasenia;
                    }
                    if (usuario.EstadoAutorizacionUsuario != null)
                    {
                        dto.EstadoAutorizacionDescripcion = usuario.EstadoAutorizacionUsuario.Nombre;
                    }
                }
                return dto;
            }
        }

        public bool EditarUsuario(UsuarioDto usuarioDto)
        {
            bool edit = false;
            using (var ctx = new UbaticEntities())
            {
                Usuario usuario = this.ObtenerUsuario(ctx, usuarioDto.Id);
                if (usuario != null)
                {
                    if (!string.IsNullOrEmpty(usuarioDto.Password))
                    {
                        UsuarioContrasenia usuarioContrasenia = ctx.UsuarioContrasenia.First();
                        if (usuarioContrasenia != null)
                        {
                            if (usuarioContrasenia.Contrasenia != usuarioDto.Password)
                            {
                                UsuarioContrasenia nuevoUsuarioContrasenia = new UsuarioContrasenia();
                                nuevoUsuarioContrasenia.Contrasenia = usuarioDto.Password;
                                nuevoUsuarioContrasenia.ForzarCambioContrasenia = usuarioDto.ForzarCambioContrasenia;
                                usuario.UsuarioContrasenia = nuevoUsuarioContrasenia;
                            }
                        }
                    }

                    EntityMapper.Map(usuarioDto, usuario);

                    edit = ctx.SaveChanges() > 0;
                }
            }

            return edit;
        }

        public Usuario ObtenerUsuario(UbaticEntities ctx, long usuarioId)
        {
            return ctx.Usuario.Where(u => u.Id == usuarioId).SingleOrDefault();
        }

        #region [Conversion]

        public List<UsuarioDto> ToDtoList(List<Usuario> usuarios)
        {
            List<UsuarioDto> dtoList = new List<UsuarioDto>();

            UsuarioDto usuarioDto = null;
            foreach (var usuario in usuarios)
            {
                usuarioDto = new UsuarioDto();
                EntityMapper.Map(usuario, usuarioDto);
                dtoList.Add(usuarioDto);
            }
            return dtoList;
        }

        #endregion [Conversion]
    }
}
